package com.corticon.dialogs.web.designer;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.HashSet;

import javax.annotation.Resource;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.ModelMap;
import org.springframework.validation.Errors;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.util.Assert;

import com.corticon.dialogs.dao.QuestionSetDAO;
import com.corticon.dialogs.dao.QuestionAvailabilityTypeDAO;
import com.corticon.dialogs.dao.QuestionDAO;
import com.corticon.dialogs.dao.QuestionStatusTypeDAO;
import com.corticon.dialogs.dao.ResponseEntryDAO;
import com.corticon.dialogs.dao.UserDAO;
import com.corticon.dialogs.dao.UserGroupDAO;
import com.corticon.dialogs.dto.PreviewQuestionDTO;
import com.corticon.dialogs.dto.QuestionDTO;
import com.corticon.dialogs.model.BasicQuestionAvailabilityType;
import com.corticon.dialogs.model.BasicQuestionStatusType;
import com.corticon.dialogs.model.PermissionType;
import com.corticon.dialogs.model.Question;
import com.corticon.dialogs.model.QuestionSet;
import com.corticon.dialogs.model.QuestionInSet;
import com.corticon.dialogs.model.ResponseEntry;
import com.corticon.dialogs.model.User;
import com.corticon.dialogs.model.UserGroup;
import com.corticon.dialogs.service.QuestionSetService;
import com.corticon.dialogs.util.CommonSupport;
import com.corticon.dialogs.util.MyComparator;
import com.corticon.dialogs.util.Pager;
import com.corticon.dialogs.util.QuestionUtils;
import com.corticon.dialogs.util.SystemMessages;
import com.corticon.dialogs.util.security.CustomUserDetails;
import com.corticon.dialogs.util.security.SecurityUtils;
import com.corticon.dialogs.validation.GenericHibernateValidator;
import com.corticon.dialogs.validation.ValidationMessageFormatter;
import com.corticon.dialogs.web.BaseController;

/**
 * @author Lifan Yang
 * @since 2008-10-06
 */
@Controller
@SessionAttributes({"questionSet", "questionsInSet"})
public class QuestionSetController extends BaseController {

    // Request URIs
    private final String NEW_VERSION_REQUEST = "newversion";
    private final String COPY_REQUEST = "copy";
    private final String WHERE_USED_REQUEST = "whereused";
    private final String QUESTIONS_REQUEST = "questions";
    private final String RETIRE_REQUEST = "retire";
    private final String PREVIEW_REQUEST = "preview";
    private final String INCLUDE_QUESTION_REQUEST = "includequestion";

    // Constants
    private final String SEQUENCE_PREFIX = "S-";

    // DAOs
    @Resource
    private QuestionAvailabilityTypeDAO questionAvailabilityTypeDAO;
    @Resource
    private QuestionStatusTypeDAO questionStatusTypeDAO;
    @Resource
    private UserGroupDAO userGroupDAO;
    @Resource
    private UserDAO userDAO;
    @Resource
    private QuestionDAO questionDAO;
    @Resource
    private QuestionSetDAO questionSetDAO;
    @Resource
    private ResponseEntryDAO responseEntryDAO;

    // Services.
    @Resource
    private QuestionSetService questionSetService;

    // Other Components
    @Resource
    private CommonSupport commonSupport;

    // Validators
    private Validator validator;

    private class Validator extends GenericHibernateValidator<QuestionSet> {

        public Validator(ValidationMessageFormatter validationMessageFormatter) {
            super.validationMessageFormatter = validationMessageFormatter;
        }

        @Override
        protected void complexValidate(Object object, Errors errors) {
            super.complexValidate(object, errors);

            QuestionSet questionSet = (QuestionSet) object;

            List<Character> vocabularyExcludedChars = new ArrayList<Character>();
            vocabularyExcludedChars.add('_');
            commonCharactersValidate(questionSet.getName(), vocabularyExcludedChars,
                    errors, "name", "questionBase.validate.vocabulary.invalidChars", null,
                    "default message");
            commonCharactersValidate(questionSet.getVocabularyname(), vocabularyExcludedChars,
                    errors, "vocabularyname", "questionBase.validate.vacabularyname.invalidChars", null,
                    "default message");
        }
    }

    @PostConstruct
    private void buildValidators() {
        validator = new Validator(validationMessageFormatter);
    }

    @Transactional(readOnly = true)
    @RequestMapping
    public String list(@RequestParam(required = false) Integer page, HttpServletRequest request, ModelMap model) {

        Pager pager = constructPager(page, request);
        pager = setPagerSort(pager, "questionSetId", request);

        request.setAttribute("pager", pager);
        request.setAttribute("order", request.getParameter("sortBy"));
        request.setAttribute("column", request.getParameter("sortColumn"));

        request.setAttribute(PAGER_NAME, pager);

        List<QuestionSet> questionSets = new ArrayList<QuestionSet>();

        // Get the permission list for the current user.
        List<String> permissions = SecurityUtils.getCurrentUserPermissions();
        // Get the current user.
        CustomUserDetails currentUserDetails = SecurityUtils.getCurrentUser();
        User currentUser = userDAO.findById(currentUserDetails.getUsername());

        // Create a questionSet example.
        QuestionSet qsExample = new QuestionSet();

        // If the current user is sys admin, he can view all of the question sets.
        if (permissions.contains(PermissionType.SYS_ADMIN.value())) {
            pager.setNumOfRecords(questionSetDAO.getCountAll());
            questionSets = questionSetDAO.getPageOfDataAll(pager);
        } else if (permissions.contains(PermissionType.PORTAL_USER.value())) {
            // If the current user is portal user, he can only view the question sets belong to him.
            qsExample.setModifiedBy(currentUser);
            pager.setNumOfRecords(questionSetDAO.getCountByExample(qsExample, "version"));
            questionSets = questionSetDAO.getPageOfDataByExample(qsExample, pager, "version");
        } else {
            // If the current user is in other roles, he can only view the question sets belong to
            // his group.
            UserGroup group = userGroupDAO.load(currentUserDetails.getGroupName());
            questionSets = questionSetDAO.getPageOfDataByGroup(group, pager);
            pager.setNumOfRecords(questionSetDAO.getCountByGroup(group));
        }

        model.addAttribute("questionSets", questionSets);

        loadUsersAndGroupsInfoByPermission(currentUserDetails, request);

        if (questionSets != null && questionSets.size() > 0) {
            request.setAttribute(DEFAULT_MESSAGE_KEY, getMessage(String.valueOf(pager.
                    getNumOfRecords()),
                    publicFoundMessage));
        } else {
            request.setAttribute(DEFAULT_MESSAGE_KEY, getMessage("", publicNotFoundMessage));
        }

        readMessages(defaultMessageKeys, request);

        return getViewNameByRequestURI(request);
    }

    /**
     * This method is executed when the user enters the create a questionset page.
     *
     * @param model
     * @param request
     * @return
     */
    @Transactional(readOnly = true)
    @RequestMapping(value = CREATE_REQUEST, method = RequestMethod.GET)
    public String getCreate(ModelMap model, HttpServletRequest request) {

        setCreate(request);

        QuestionSet qs = new QuestionSet();
        // Update it By Last Commit 2009.7.1
//        // Init the QuestionSet for the create operation.
//        String maxRefId = questionSetDAO.getMaxRefId();
//        if (maxRefId == null) {
//            qs.setRefId(QuestionUtils.PAGE_REFID_PREFIX + QuestionUtils.REFID_BEGIN);
//        } else {
//            qs.setRefId(QuestionUtils.getNextRefId(maxRefId));
//        }
        qs.setVersion(1l);
        qs.setAvailability(questionAvailabilityTypeDAO.load(BasicQuestionAvailabilityType.open.
                toString()));
        qs.setStatus(questionStatusTypeDAO.load(BasicQuestionStatusType.active.toString()));

        model.addAttribute("questionSet", qs);

        request.setAttribute(DEFAULT_MESSAGE_KEY, getMessage("common.action.prompt"));

        readMessages(defaultMessageKeys, request);

        return getViewNameByRequestURI(request);
    }

    /**
     * Process the create a questionset operation.
     *
     * @param questionSet
     * @param request
     * @return
     */
    @RequestMapping(value = {CREATE_REQUEST, NEW_VERSION_REQUEST, COPY_REQUEST}, method = RequestMethod.POST)
    public String postCommonCreate(@ModelAttribute QuestionSet questionSet, Errors errors, HttpServletRequest request,
                                   SessionStatus sessionStatus) throws JSONException {

        // Get the parameters.
        String submitAction = request.getParameter(SUBMIT_ACTION);

        if (request.getRequestURI().indexOf(CREATE_REQUEST) > -1) {
            setCreate(request);
        } else if (request.getRequestURI().indexOf(NEW_VERSION_REQUEST) > -1) {
            request.setAttribute(OPERATION_KEY, NEW_VERSION_REQUEST);
        } else if (request.getRequestURI().indexOf(COPY_REQUEST) > -1) {
            request.setAttribute(OPERATION_KEY, COPY_REQUEST);
        }

        Map<String, List<String>> errorMessages = validator.invokeValidate(questionSet, errors,
                request.getLocale());

        if (errors.hasErrors()) {
            request.setAttribute(ERROR_MESSAGE_KEY, errorMessages);
            return getViewNameByRequestURI(request);
        }

        // Declare args for messages.
        Object[] args = {questionSet.getRefId(), questionSet.getVersion()};

        // Check whether the refId and version combination is duplicate.
        if (questionSetDAO.findByRefIdAndVersion(questionSet.getRefId(), questionSet.getVersion()) != null) {
            addMessages(DEFAULT_MESSAGE_KEY + "=questionBase.refIdAndVersionDuplicate", request, messageSource, args);
            return redirectPrefix + CREATE_REQUEST + requestSuffix;
        }

        List<QuestionDTO> dtos = getQuestionsInfo(request, "questions");
        // Update by Last commit 2009.7.1
        // Init the QuestionSet for the create operation.
        String maxRefId = questionSetDAO.getMaxRefId();
        if (request.getRequestURI().indexOf(NEW_VERSION_REQUEST) == -1){
        	if (maxRefId == null && questionSet.getVersion()==1L) {
            	questionSet.setRefId(QuestionUtils.PAGE_REFID_PREFIX + QuestionUtils.REFID_BEGIN);
            } else {
            	questionSet.setRefId(QuestionUtils.getNextRefId(maxRefId));
            }
        }
        
        questionSetService.saveOrUpdate(questionSet, dtos);

        sessionStatus.setComplete();

        if (request.getRequestURI().indexOf(CREATE_REQUEST) > -1) {
            addMessages(DEFAULT_MESSAGE_KEY + "=questionSet.create.success", request, messageSource,
                    null);
        } else if (request.getRequestURI().indexOf(NEW_VERSION_REQUEST) > -1) {
            addMessages(DEFAULT_MESSAGE_KEY + "=questionSet.newversion.success", request,
                    messageSource, null);
        } else if (request.getRequestURI().indexOf(COPY_REQUEST) > -1) {
            addMessages(DEFAULT_MESSAGE_KEY + "=questionSet.copy.success", request, messageSource,
                    null);
        }

        return redirectBySubmitAction(submitAction, "id=" + questionSet.getQuestionSetId(), request);
    }

    @Transactional(readOnly = true)
    @RequestMapping(value = EDIT_REQUEST, method = RequestMethod.GET)
    public String getEdit(@RequestParam Long id, ModelMap model, HttpServletRequest request) {

        setEdit(request);

        QuestionSet questionSet = questionSetDAO.findById(id);

        if (questionSet == null) {
            return entityNotFoundViewName;
        }

        if (!commonWritePermissionCheck(questionSet)) {
            return redirectTo403();
        }

        if (!commonSupport.checkQuestionAvailability(questionSet).equals(
                SystemMessages.ACTION_SUCCESS)) {
            return redirectTo403();
        }

        model.addAttribute("questionSet", questionSet);
        model.addAttribute("questionsInSet", questionSet.getSortedQuestions());

        request.setAttribute(DEFAULT_MESSAGE_KEY, getMessage("common.action.prompt"));

        readMessages(defaultMessageKeys, request);

        return getViewNameByRequestURI(request);
    }

    @RequestMapping(value = EDIT_REQUEST, method = RequestMethod.POST)
    public String postEdit(@ModelAttribute QuestionSet questionSet, Errors errors,
                           HttpServletRequest request, SessionStatus sessionStatus) throws JSONException {

        setEdit(request);

        String target = request.getParameter(SUBMIT_ACTION);

        Map<String, List<String>> errorMessages = validator.invokeValidate(questionSet, errors,
                request.getLocale());

        if (errors.hasErrors()) {
            request.setAttribute(ERROR_MESSAGE_KEY, errorMessages);
            return getViewNameByRequestURI(request);
        }

        List<QuestionDTO> dtos = getQuestionsInfo(request, "questions");

        questionSetService.saveOrUpdate(questionSet, dtos);

        sessionStatus.setComplete();

        addMessages(DEFAULT_MESSAGE_KEY + "=questionSet.edit.success", request, messageSource, null);

        return redirectBySubmitAction(target, "id=" + questionSet.getQuestionSetId(), request);
    }

    @Transactional(readOnly = true)
    @RequestMapping(value = NEW_VERSION_REQUEST, method = RequestMethod.GET)
    public String getNewVersion(@RequestParam Long id, ModelMap model, HttpServletRequest request) throws
            IllegalAccessException, InvocationTargetException {

        QuestionSet original = questionSetDAO.findById(id);

        if (original == null) {
            return entityNotFound();
        }

        if (!commonWritePermissionCheck(original)) {
            return redirectTo403();
        }

        if (original.getStatus().getName().equals(BasicQuestionStatusType.retired.toString())) {
            return redirectTo403();
        }

        QuestionSet newOne = new QuestionSet();
        BeanUtils.copyProperties(newOne, original);

        newOne.setQuestionSetId(null);
        // Update by Last commit 2009.7.1
        newOne.setRefId(original.getRefId());        
        long maxVersion = questionSetDAO.getMaxVersionByRefId(original.getRefId());
        newOne.setVersion(maxVersion + 1);
        newOne.setAvailability(questionAvailabilityTypeDAO.load(BasicQuestionAvailabilityType.open.
                toString()));
        newOne.setStatus(questionStatusTypeDAO.load(BasicQuestionStatusType.active.toString()));
        newOne.setModifiedBy(null);
        newOne.setModifiedOn(null);
        newOne.setRetiredBy(null);
        newOne.setRetiredNote(null);
        newOne.setRetiredOn(null);

        // Copy the links between the QuestionSet and the Questions.
//        copyLinks(original, newOne);
        newOne.setQuestionsInSet(new HashSet<QuestionInSet>());

        model.addAttribute(newOne);
        model.addAttribute("questionsInSet", original.getQuestionsInSet());

        request.setAttribute(OPERATION_KEY, NEW_VERSION_REQUEST);
        request.setAttribute(DEFAULT_MESSAGE_KEY,
                "Please make the desired changes and choose Save or Cancel");

        return getViewNameByRequestURI(request);
    }

    @Transactional(readOnly = true)
    @RequestMapping(value = COPY_REQUEST, method = RequestMethod.GET)
    public String getCopy(@RequestParam Long id, ModelMap model, HttpServletRequest request) throws
            IllegalAccessException, InvocationTargetException {

        request.setAttribute(OPERATION_KEY, COPY_REQUEST);

        QuestionSet original = questionSetDAO.findById(id);

        if (original == null) {
            return entityNotFound();
        }

        if (!commonWritePermissionCheck(original)) {
            return redirectTo403();
        }

        QuestionSet newOne = new QuestionSet();
        BeanUtils.copyProperties(newOne, original);

        // Some properties should be reset.
        newOne.setQuestionSetId(null);
        // Update by Last commit 2009.7.1
//        String maxRefId = questionSetDAO.getMaxRefId();
//        if (maxRefId == null) {
//            newOne.setRefId(QuestionUtils.PAGE_REFID_PREFIX + QuestionUtils.REFID_BEGIN);
//        } else {
//            newOne.setRefId(QuestionUtils.getNextRefId(maxRefId));
//        }
        newOne.setRefId(null);
        newOne.setVersion(1l);
        newOne.setStatus(questionStatusTypeDAO.load(BasicQuestionStatusType.active.toString()));
        newOne.setModifiedBy(null);
        newOne.setModifiedOn(null);
        newOne.setRetiredBy(null);
        newOne.setRetiredNote(null);
        newOne.setRetiredOn(null);

//        copyLinks(original, newOne);
        newOne.setQuestionsInSet(new HashSet<QuestionInSet>());

        model.addAttribute(newOne);
        model.addAttribute("questionsInSet", original.getQuestionsInSet());

        request.setAttribute(OPERATION_KEY, COPY_REQUEST);
        request.setAttribute(DEFAULT_MESSAGE_KEY,
                "Please make the desired changes and choose Save or Cancel");

        return getViewNameByRequestURI(request);
    }

    @RequestMapping(value = VIEW_REQUEST, method = RequestMethod.GET)
    public String view(@RequestParam Long id, HttpServletRequest request, ModelMap model) {
        request.setAttribute(OPERATION_KEY, VIEW_REQUEST);

        QuestionSet questionSet = questionSetDAO.findById(id);

        if (questionSet == null) {
            return entityNotFound();
        }

        if (!checkReadPermission(questionSet)) {
            return redirectTo403();
        }

        request.setAttribute("questionSet", questionSet);

        readMessages(defaultMessageKeys, request);

        return getViewNameByRequestURI(request);
    }

    @Transactional
    @RequestMapping(value = QUESTIONS_REQUEST, method = RequestMethod.POST)
    public String postQuestions(HttpServletRequest request) {
        Long qsId = Long.valueOf(request.getParameter("id"));
        String submitAction = request.getParameter(SUBMIT_ACTION);

        QuestionSet questionSet = questionSetDAO.findById(qsId);

        String newSequence = null;
        for (QuestionInSet qis : questionSet.getQuestionsInSet()) {
            newSequence = request.getParameter(SEQUENCE_PREFIX + qis.getSequence());
            qis.setSequence(newSequence);

            Double priority = QuestionUtils.calculatePriority(newSequence);
            qis.setPriority(priority);
        }

        Question q1502 = questionDAO.findById(1502l);
        QuestionInSet newQis = new QuestionInSet(q1502, questionSet, "10", 10d);
        questionSet.getQuestionsInSet().add(newQis);

        questionSetDAO.saveOrUpdate(questionSet);

        return redirectBySubmitAction(submitAction, "id=" + questionSet.getQuestionSetId(), request);
    }

    @RequestMapping(method = RequestMethod.GET)
    public String search(@RequestParam(required = false) Integer page, HttpServletRequest request) {
        // Get the search criterions.
        String keywords = request.getParameter("keywords");
        String availability = request.getParameter("availability");
        availability = availability == null ? "" : availability;
        String status = request.getParameter("status");
        status = status == null ? "" : status;
        String username = request.getParameter("user");
        username = username == null ? "" : username;
        String groupName = request.getParameter("group");
        groupName = groupName == null ? "" : groupName;

        // Construct the pager object.
        Pager pager = constructPager(page, request);

        // Add sort info.
        pager = setPagerSort(pager, "questionSetId", request);
        request.setAttribute("pager", pager);
        request.setAttribute("order", request.getParameter("sortBy"));
        request.setAttribute("column", request.getParameter("sortColumn"));

        String requestParameters = "&keywords=" + keywords + "&availability=" + availability +
                "&status=" + status;

        // Construct the search criterions according to the role of the current user. And set the 
        // username and groupname params into the request scope.
        CustomUserDetails currentUser = SecurityUtils.getCurrentUser();
        List<String> permissions = currentUser.getPermissions();
        if (permissions.contains(PermissionType.GROUP_ADMIN.value()) || permissions.contains(
                PermissionType.AUTHOR.value()) || permissions.contains(PermissionType.REVIEWER.value())) {
            groupName = currentUser.getGroupName();
            requestParameters = requestParameters + "&group=" + groupName;
        } else if (permissions.contains(PermissionType.PORTAL_USER.value())) {
            username = currentUser.getUsername();
            requestParameters = requestParameters + "&user=" + username;
        } else {
            requestParameters = requestParameters + "&user=" + username + "&group=" + groupName;
        }

        // Set the search criterions into the pager object.
        pager.setRequestParameters(pager.getRequestParameters() + requestParameters);

        List<QuestionSet> results = new ArrayList<QuestionSet>();

        if (StringUtils.isNotEmpty(username) && StringUtils.isNotEmpty(groupName)) {
            User searchedUser = userDAO.findById(username);
            if (searchedUser != null && searchedUser.getUserGroup().getName().equals(groupName)) {
                // Call the search method in Question Set DAO to search. Then set results into the request
                // scope.
                results = questionSetDAO.search(keywords, availability, status, username, groupName,
                        pager);
            }
        } else {
            results = questionSetDAO.search(keywords, availability, status, username, groupName,
                    pager);
        }

        // Set the pager and results into the request scope.
        request.setAttribute("pager", pager);
        request.setAttribute("questionSets", results);

        // Call the method to get infos about User and Group according to the role of the current
        // user. Then set them into the request scope.
        loadUsersAndGroupsInfoByPermission(currentUser, request);

        if (results != null && results.size() > 0) {
            request.setAttribute(DEFAULT_MESSAGE_KEY, getMessage(String.valueOf(pager.
                    getNumOfRecords()), publicFoundMessage));
        } else {
            request.setAttribute(DEFAULT_MESSAGE_KEY, getMessage("", publicNotFoundMessage));
        }

        return getViewNameByRequestURI(request);
    }

    @RequestMapping(method = RequestMethod.GET)
    public String preview(@RequestParam Long id, HttpServletRequest request) {
        // Get a questionSet object according to the id.
        QuestionSet questionSet = questionSetDAO.findById(id);
        Assert.notNull(questionSet);

        request.setAttribute("questions", toPreviewDTOList(questionSet));

        return getViewNameByRequestURI(request);
    }

    @RequestMapping(value = PREVIEW_REQUEST, method = RequestMethod.POST)
    public String previewOnEdit(HttpServletRequest request) throws JSONException {

        // questionsData is in the format of JSON.
        String questionsData = request.getParameter("questions");

        // Construct a JSONObject according to the questionsData.
        JSONArray questions = new JSONArray(questionsData);

        List<PreviewQuestionDTO> dtos = new ArrayList<PreviewQuestionDTO>();

        for (int i = 0; i < questions.length(); i++) {
            JSONObject questionJSON = questions.getJSONObject(i);

            Question question =
                    questionDAO.findById(((Integer) questionJSON.get("qId")).longValue());

            if (question != null) {
                PreviewQuestionDTO dto = new PreviewQuestionDTO();
                dto.setResponseType(question.getResponseType().getName());

                if (question.getResponseType().getUicontrol() != null) {
                    dto.setUiType(question.getResponseType().getUicontrol().getName());
                }

                // Set other properties from Question to PreviewQuestionDTO.
                String sequence = questionJSON.getString("sequence");
                dto.setSequence(sequence);
                dto.setPriority(QuestionUtils.calculatePriority(sequence));

                dto.setText(question.getText());
                dto.setWidth(question.getWidth());
                dto.setResponses(responseEntryDAO.findByResponseType(question.getResponseType()));

                dtos.add(dto);
            }
        }

        // Sort the PreviewQuestionDTO list.
        Collections.sort(dtos);

        // Set the PreviewQuestionDTO list into the request scope.
        request.setAttribute("questions", dtos);

        return getViewNameByRequestURI(request);
    }

    @RequestMapping(value = RETIRE_REQUEST, method = RequestMethod.GET)
    public String getRetire(@RequestParam Long id, HttpServletRequest request, ModelMap model) {

        QuestionSet questionSet = questionSetDAO.findById(id);

        if (questionSet == null) {
            return entityNotFound();
        }

        if (!checkRetirePermission(questionSet)) {
            return redirectTo403();
        }

        model.addAttribute(questionSet);

        // Set a message.
        request.setAttribute(DEFAULT_MESSAGE_KEY, getMessage("questionSet.retire.note.required"));

        return getViewNameByRequestURI(request);
    }

    @Transactional
    @RequestMapping(value = RETIRE_REQUEST, method = RequestMethod.POST)
    public String postRetire(@ModelAttribute QuestionSet questionSet, Errors errors,
                             HttpServletRequest request, SessionStatus sessionStatus) {

        String submitAction = request.getParameter(SUBMIT_ACTION);

        // If the retired note is empty, return to the form, and display a message.
        if (StringUtils.isEmpty(questionSet.getRetiredNote())) {
            request.setAttribute(DEFAULT_MESSAGE_KEY, getMessage(
                    "questionSet.validate.retiredNote.required"));
            return getViewNameByRequestURI(request);
        }

        // Set the status is retired.
        questionSet.setStatus(questionStatusTypeDAO.load(BasicQuestionStatusType.retired.toString()));

        // Set the retiredOn.
        questionSet.setRetiredOn(new Date());

        // Set the retiredBy.
        CustomUserDetails currentUser = SecurityUtils.getCurrentUser();
        User operator = userDAO.load(currentUser.getUsername());
        questionSet.setRetiredBy(operator);

        // Set the modifiedBy.
        questionSet.setModifiedBy(operator);

        // Update the question set record.
        questionSetDAO.update(questionSet);

        // Session Objects will not be used anymore.
        sessionStatus.setComplete();

        // Set the message.
        String message = getMessage("questionSet.retire.success");
        request.getSession().setAttribute(DEFAULT_MESSAGE_KEY, message);

        return redirectBySubmitAction(submitAction, "id=" + questionSet.getQuestionSetId(), request);
    }

    @RequestMapping(value = INCLUDE_QUESTION_REQUEST, method = RequestMethod.GET)
    public String includeQuestion(@RequestParam Long qId, @RequestParam Integer maxSequence, HttpServletRequest request) {
        QuestionDTO dto = questionSetService.includeQuestion(qId, maxSequence);
        request.setAttribute("question", dto);
        return getViewNameByRequestURI(request);
    }

    // ********** Inner Methods Begin ********** //
    /**
     * The common write operation include the edit, copy, delete, new verion operations. This method
     * is used to check whether a user has the appropriate pemissions to do the write operation.
     *
     * @return
     */
    private boolean commonWritePermissionCheck(QuestionSet questionSet) {

        List<String> permissions = SecurityUtils.getCurrentUserPermissions();
        CustomUserDetails currentUser = SecurityUtils.getCurrentUser();

        // If the current user is an author, he can only operate the question set in the same group
        // with him.
        if (permissions.contains(PermissionType.AUTHOR.value())) {
            if (questionSet.getModifiedBy().getUserGroup().getName().equals(
                    currentUser.getGroupName())) {
                return true;
            } else {
                return false;
            }
        }

        // If the current user is a portal user, he can only operate the question set he owned.
        if (permissions.contains(PermissionType.PORTAL_USER.value())) {
            if (questionSet.getModifiedBy().getUsername().equals(currentUser.getUsername())) {
                return true;
            } else {
                return false;
            }
        }

        return false;
    }

    /**
     * This method is used to check whether the current user has the right permission to execute the
     * read operation, like the view operation.
     *
     * @param questionSet
     * @return
     */
    private boolean checkReadPermission(QuestionSet questionSet) {

        CustomUserDetails currentUser = SecurityUtils.getCurrentUser();

        if (currentUser.getPermissions().contains(PermissionType.SYS_ADMIN.value())) {
            return true;
        } else if (currentUser.getPermissions().contains(PermissionType.PORTAL_USER.value())) {
            if (currentUser.getUsername().equals(questionSet.getModifiedBy().getUsername())) {
                return true;
            } else {
                return false;
            }
        } else {
            if (currentUser.getGroupName().equals(questionSet.getModifiedBy().getUserGroup().getName())) {
                return true;
            } else {
                return false;
            }
        }
    }

    private boolean checkRetirePermission(QuestionSet questionSet) {
        CustomUserDetails currentUser = SecurityUtils.getCurrentUser();

        if (questionSet.getStatus().getName().equals(BasicQuestionStatusType.retired.toString())) {
            return false;
        }

        if (currentUser.getPermissions().contains(PermissionType.PORTAL_USER.value())) {
            if (currentUser.getUsername().equals(questionSet.getModifiedBy().getUsername())) {
                return true;
            } else {
                return false;
            }
        } else if (currentUser.getPermissions().contains(PermissionType.REVIEWER.value())) {
            if (currentUser.getGroupName().equals(questionSet.getModifiedBy().getUserGroup().getName())) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    private void loadUsersAndGroupsInfoByPermission(CustomUserDetails currentUser, HttpServletRequest request) {
        List<String> permissions = currentUser.getPermissions();

        if (permissions.contains(PermissionType.GROUP_ADMIN.value()) || permissions.contains(
                PermissionType.AUTHOR.value()) || permissions.contains(PermissionType.REVIEWER.value())) {
            List<User> users = userDAO.findByGroup(currentUser.getGroupName());
            request.setAttribute("users", users);
        } else if (permissions.contains(PermissionType.SYS_ADMIN.value())) {
            List<User> users = userDAO.findAll();
            request.setAttribute("users", users);
            List<UserGroup> groups = userGroupDAO.findAll();
            request.setAttribute("groups", groups);
        }
    }

    private List<PreviewQuestionDTO> toPreviewDTOList(QuestionSet questionSet) {
        List<PreviewQuestionDTO> dtos = new ArrayList<PreviewQuestionDTO>();

        for (QuestionInSet qis : questionSet.getQuestionsInSet()) {
            Question question = qis.getQuestion();

            PreviewQuestionDTO pqdto = new PreviewQuestionDTO();

            // Set the response type of the question.
            pqdto.setResponseType(question.getResponseType().getName());

            if (question.getResponseType().getUicontrol() != null) {
                pqdto.setUiType(question.getResponseType().getUicontrol().getName());
            }

            // Set other properties from Question to PreviewQuestionDTO.
            pqdto.setSequence(qis.getSequence());
            pqdto.setText(question.getText());
            List<ResponseEntry> oldResponses = responseEntryDAO.findByResponseType(question.getResponseType());
			Object[] allData = oldResponses.toArray();
			if (allData != null) {
				Arrays.sort(allData, new MyComparator());
				List<ResponseEntry> NewResponses = new ArrayList();
				for (int i = 0; i < allData.length; i++) {
					NewResponses.add((ResponseEntry) allData[i]);
				}
				pqdto.setResponses(NewResponses);
			}
            //pqdto.setResponses(responseEntryDAO.findByResponseType(question.getResponseType()));
            pqdto.setWidth(question.getWidth());

            dtos.add(pqdto);
        }

        Collections.sort(dtos);
        return dtos;
    }

    /**
     * This method get the questions info from HttpRequest according to the paramId. The questions
     * info is in the format of JSON.
     *
     * @param request
     * @param paramId
     * @return
     * @throws org.json.JSONException
     */
    private List<QuestionDTO> getQuestionsInfo(HttpServletRequest request, String paramId) throws
            JSONException {
        // questionsData is in the format of JSON.
        String questionsData = request.getParameter(paramId);

        // Construct a JSONObject according to the questionsData.
        JSONArray questions = new JSONArray(questionsData);

        List<QuestionDTO> dtos = new ArrayList<QuestionDTO>();

        for (int i = 0; i < questions.length(); i++) {
            JSONObject questionJSON = questions.getJSONObject(i);
            QuestionDTO dto = new QuestionDTO();
            dto.setQisId(questionJSON.getString("qisId"));
            dto.setQuestionId(Long.parseLong(questionJSON.getString("questionId")));
            dto.setSequence(questionJSON.getString("sequence"));

            dtos.add(dto);
        }

        return dtos;
    }
    // ********** Inner Methods End ********** //
}